"""
Frame for displaying the result of the CRC calculation.
"""

import tkinter as tk
import tkinter.messagebox as messagebox
import tkinter.ttk as ttk
import tkinter.font as font
import threading
import time
from crccalc import calccrc, calccrc_with_log
from ui.PolynomialConversion import *

class CrcResultFrame(tk.Frame):
    """This frame contains the result of the CRC calculation.

    Maybe in this area will be contained the loading bar, the
    start button, and similar things."""
    def create_start_button(self):
        """Create the button for starting the CRC calculation."""
        self.launchButton = tk.Button(self)
        self.launchButton["text"] = "Calculate"
        self.launchButton["font"] = font.Font(size=12)
        self.launchButton["fg"] = "#ffffff"
        self.launchButton["bg"] = "#3fb51f"
        def cb(*args):
            dg = self.parent.inputFrame.getDataGenerator()
            polynomialChoiceFrame = self.parent.polynomialChoiceFrame
            polynomial = polynomialChoiceFrame.get_polynomial()
            polynomialLength = polynomialChoiceFrame.get_polynomial_length()
            if self.calculationStarted:
                with self.allLock:
                    self.requestEarlyExit = True
            elif dg is not None and polynomial is not None:
                self.launchButton["text"] = "Cancel"
                self.launchButton["fg"] = "#ffffff"
                self.launchButton["bg"] = "#ee0011"

                # Display the progressbar.
                self.resultPresentingLabel.grid_remove()
                self.crcValueLabel.grid_remove()
                self.crcValueText.grid_remove()
                self.progressbar.grid(column=1, row=0, sticky=tk.E)

                self.run_crc_calculations(
                    dg,
                    self.parent.inputFrame.getDataSize(),
                    polynomial,
                    polynomialLength)
        self.launchButton["command"] = cb
        self.launchButton.grid(column=0, row=0, sticky=tk.W, padx=5, pady=5)

    def reset_calculations_sync_vars(self):
        # Only one lock is used for all the variables.
        self.allLock = threading.Lock()
        self.calculationStarted = False
        self.calculationOver = False
        self.requestEarlyExit = False
        self.bitsUsed = 0

    def create_help_button(self):
        """Create the help button."""
        def display_help():
            text = """Click on "Calculate" to start the CRC computing.
Press "Cancel" to stop the calculation.

If the data is big, this make take some time.

Once the CRC is calculated you may select it (for example,
to copy paste it) by hovering over it."""
            messagebox.showinfo('Help', text)
        self.help_button = tk.Button(self, text='?', command=display_help)
        self.help_button.grid(column=3, row=0, sticky=tk.E, padx=5, pady=5)
        
    def create_widgets(self):
        """Create the frame starting layout."""
        self.progressbar = ttk.Progressbar(self, orient=tk.HORIZONTAL, length=100, mode='determinate')
        self.progressbar["maximum"] = 1
        self.progressbar["value"] = 0
        self.progressbar.grid(column=1, row=0, sticky=tk.E, padx=5, pady=5)
        self.crcValueLabel = tk.Label(self, textvariable=self.crc)
        self.crcValueLabel.bind('<Enter>', self.update_crc_widget_on_enter)
        self.crcValueText = tk.Text(self) # Not display by default.
        self.crcValueText["width"] = "15" # columns
        self.crcValueText["height"] = "1" # lines
        self.crcValueText["wrap"] = "none"
        self.crcValueText.bind('<Leave>', self.update_crc_widget_on_leave)
        self.resultPresentingLabel = tk.Label(self, text='The CRC is:') # The same.
        self.create_start_button()
        self.create_help_button()

    def update_crc_widget_on_enter(self, event):
        self.crcValueLabel.grid_remove();
        self.crcValueText.grid(column=2, row=0, sticky=tk.W)
        self.crcValueText.tag_remove(tk.SEL, 1.0, tk.END)
        # Don't add a tag from 1.0 to END. In fact the '\n' is taken in account.
        self.crcValueText.tag_add(tk.SEL, 1.0, '1.'+str(len(self.crcCalculationResult)))
        self.crcValueText.focus_set()

    def update_crc_widget_on_leave(self, event):
        self.crcValueText.grid_remove();
        self.crcValueLabel.grid(column=2, row=0, sticky=tk.W)

    def set_crc_result_val(self):
        self.resultPresentingLabel['text'] = 'The CRC is:'
        if self.verboseModeOn:
            self.crc.set(get_text_polynomial_from_binary_string(self.crcCalculationResult))
        else:
            self.crc.set(self.crcCalculationResult)

        self.crcValueText.config(state=tk.NORMAL)
        self.crcValueText.delete(1.0, tk.END)
        self.crcValueText.insert(tk.INSERT,self.crcCalculationResult)
        self.crcValueText.config(state=tk.DISABLED)

        self.crcValueLabel.grid(column=2, row=0, sticky=tk.W)
        
    def __init__(self,master=None):
        tk.Frame.__init__(self, master)
        self.parent = master
        self.crc = tk.StringVar()
        self.crcCalculationResult = ('','')
        self["relief"] = "sunken"
        self["borderwidth"] = "2"
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)
        self.create_widgets()
        self.reset_calculations_sync_vars()
    
    def run_crc_calculations(self,dataGen,dataSize,polynomial,polynomialLength):
        """Starts the crc calculations in another thread, and update the
        progress bar."""
        self.requestEarlyExit = False
        self.verboseModeOn = self.parent.parent.menubar.verboseMode.get() \
            and self.parent.parent.mFrame.inputFrame.verboseModeAllowed \
            and polynomialLength < 17
        def crc_calc_wrap():
            def dataGenWithPadding():
                for bit in dataGen:
                    yield bit
                for i in range(polynomialLength):
                    yield 0
            """This function run crccalc. To be called in a thread.

            It also redefines dataGen in order to update the number of bytes
            used, and to enable calculation cancellation."""
            def dataGenWithProgress():
                    """Same as dataGen, but with permature cancellation and
                    progress report."""
                    for bit in dataGenWithPadding():
                        yield bit
                        with self.allLock:
                            self.bitsUsed +=1
                            if self.requestEarlyExit:
                                return
                        if self.verboseModeOn:
                            while self.parent.calculationDetailsFrame.lock:
                                if self.requestEarlyExit:
                                    return
                                time.sleep(0.1)
                            self.parent.calculationDetailsFrame.lock = True
                    
            self.calculationStarted = True
            dg = dataGenWithProgress()
            # Verbose mode is enabled ?
            if self.verboseModeOn:
                ret = calccrc_with_log(dg,
                                       polynomial,
                                       self)
            else:
                ret = calccrc(dg, polynomial)
            with self.allLock:
                self.crcCalculationResult = ret
                self.calculationOver = True
            dg.close()
        def crc_calc_watcher():
            with self.allLock:
                calculationDetailsFrame = self.parent.calculationDetailsFrame
                if self.verboseModeOn:
                    calculationDetailsFrame.update(self.crcCurrentCalculated)
                    # Autorun.
                    if calculationDetailsFrame.autorun:
                        if time.time() - calculationDetailsFrame.timeLastStep > 0.5:
                            calculationDetailsFrame.timeLastStep = time.time()
                            calculationDetailsFrame.nextLoop()
                # Over.
                if self.calculationOver:
                    self.calcThread.join()
                    self.calculationStarted = False
                    # Display the result.
                    self.progressbar.grid_remove()
                    self.resultPresentingLabel.grid(column=1, row=0, sticky=tk.E)
                    if self.requestEarlyExit:
                        self.parent.calculationDetailsFrame.delete_widgets()
                        self.parent.calculationDetailsFrame.grid_remove()
                        self.crc.set('')
                        self.resultPresentingLabel['text'] = 'Cancelled.'
                    else:
                        self.set_crc_result_val()
                    self.launchButton["text"] = "Calculate"
                    self.launchButton["fg"] = "#ffffff"
                    self.launchButton["bg"] = "#3fb51f"
                    self.reset_calculations_sync_vars()
                else:
                    if self.bitsUsed <=dataSize+polynomialLength:
                        self.progressbar["value"] = self.bitsUsed
                    else:
                        self.progressbar["value"] = dataSize
                    self.after(200,crc_calc_watcher)
        self.crcCurrentCalculated = bin(polynomial)[2:]
        if self.verboseModeOn:
            self.parent.calculationDetailsFrame.grid(column=0,row=1,columnspan=2,sticky=tk.N+tk.S+tk.E+tk.W)
            self.parent.calculationDetailsFrame.create_widgets(polynomial)
        else:
            self.parent.calculationDetailsFrame.delete_widgets()
            self.parent.calculationDetailsFrame.grid_remove()
        self.progressbar["maximum"] = dataSize+polynomialLength
        self.progressbar["value"] = 0
        self.calcThread = threading.Thread(
            target=crc_calc_wrap,
            name="crc calculation")
        self.calcThread.start()
        self.after(0,crc_calc_watcher)
